﻿using System;
using System.Linq;
using System.Text;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

namespace XGame.Animation
{

    /// <summary>
    /// The SimpleAnimation2D class is for simple, basic animations that only require a left-to-right movement at a constant rate.
    /// The class also expects that the frames are the same width and height and are side-by-side with no gaps between them.
    /// 
    /// For more complex 2D then use the <seealso cref="ComplexAnimation2D"/> class
    /// </summary>
    public sealed class SimpleAnimation2D : XUpdate
    {

        private Int32 iInternalCounter = 0;

        /// <summary>
        /// Delay represents the number of frames to wait before the Animation class advances one frame forward.
        /// </summary>
        public Int32 Delay { get; set; }

        /// <summary>
        /// A copy of the Texture that is currently used for the animation
        /// </summary>
        public Texture2D Texture { get; private set; }

        /// <summary>
        /// The row property is the current row on the animation sheet.
        /// The animation class only plays a single row at a given time and uses the Row property * the height of the frame rectangle to determine where
        /// to place the frame rectangle.
        /// </summary>
        public Int32 Row { get; set; }

        /// <summary>
        /// The reference rectangle is used to describe the size of the frame; the width and the height.
        /// This allows for the reference frame size to change at any given time.
        /// Please note that the X and Y properties of this reference rectangle will not be utilized.
        /// </summary>
        public Rectangle Reference { get; set; }

        /// <summary>
        /// The current frame on the SpriteSheet.
        /// </summary>
        public Rectangle Frame { get; private set; }

        /// <summary>
        /// Hides the default constructor to the Animation class
        /// </summary>
        private SimpleAnimation2D() { }

        /// <summary>
        /// Creates a new Animation class with the supplied SpriteSheet and a rectangle
        /// that represents a single frame of that given SpriteSheet
        /// </summary>
        /// <param name="aSpriteSheet">The SpriteSheet that contains all the frames for this animation</param>
        /// <param name="aFrameSize">The width and height of the frame</param>
        public SimpleAnimation2D(Texture2D aSpriteSheet, Rectangle aFrameSize)
        {
            Texture = aSpriteSheet;

        }

        public void Update(GameTime aTime)
        {

            // Checking for when we can move the frame up by one.
            if (iInternalCounter > 0 && Delay == iInternalCounter)
            {

                // Set the counter to negative one.
                iInternalCounter = -1;

                // From the current frame's dimensions, figure out whether or not
                // if moving over one frame will push us off the sheet
                Rectangle oNewFrame = Frame;
                oNewFrame.X = Frame.X + Frame.Width + Reference.Width > Texture.Width ? 0 : Frame.X + Frame.Width;
                Frame = oNewFrame;

            }

            // Increment the counter
            iInternalCounter++;

        }

        public void Initialize() { }

    }

    /// <summary>
    /// The animation class takes a single "SpriteSheet", a Texture2D object, and allows for fine grained control of
    /// how it is represented and animated on the screen. The animation class merely moves a frame rectangle around
    /// on the Texture2D object and exposes it via ReadOnly properties.
    /// </summary>
    public sealed class ComplexAnimation2D : XUpdate
    {

        /// <summary>
        /// iCurrentAnimationSet stores the String value that is used to access the current collection of frames
        /// </summary>
        private String iCurrentAnimationSet;

        /// <summary>
        /// The number of update calls (frames) that have passed
        /// </summary>
        private Int32 iCurrentFrameCount;


        /// <summary>
        /// The sheet that is being animated by the class
        /// </summary>
        public Texture2D SpriteSheet { get; private set; }

        /// <summary>
        /// The current collection of frames for the given animation object
        /// </summary>
        public FrameCollection Frames { get; private set; }

        /// <summary>
        /// The current frame of the animation class
        /// </summary>
        public FrameCollection.Frame CurrentFrame { get; private set; }

        /// <summary>
        /// Creates a new instance of the Animation class with the given sprite sheet
        /// </summary>
        /// <param name="aSheet">The Texture2D object to animate</param>
        public ComplexAnimation2D(Texture2D aSheet)
        {
            Frames = new FrameCollection();
            SpriteSheet = aSheet;
            iCurrentFrameCount = 0;
        }

        /// <summary>
        /// Updates the Animation class and shifts to the next frame in the current sequence
        /// </summary>
        /// <param name="aTime"></param>
        public void Update(GameTime aTime)
        {

            // Check the current frame for null. If it's null, the class hasn't been told to animate anything
            if (CurrentFrame != null)
            {

                // Increment by one
                iCurrentFrameCount++;

                // Now check
                if (iCurrentFrameCount > CurrentFrame.Frames)
                {

                    // Reset the counter
                    iCurrentFrameCount = 0;

                    // Get the next frame
                    fGetNextFrame();

                }

            }

        }

        /// <summary>
        /// Initializes the Animation class
        /// </summary>
        public void Initialize()
        {

            // Grab the first frame of the given animated selection
            if (!Frames.Any()) throw new ArgumentException("There are no frames to animate. Please add frames to the FrameCollection object");
            if (iCurrentAnimationSet == null) throw new ArgumentException("Please call Animate(String) before Initializing the object");
            if (SpriteSheet == null) throw new ArgumentException("You must have a SpriteSheet to animate");
            
        }

        /// <summary>
        /// Sets the animation class to a given collection to animate
        /// </summary>
        /// <param name="aName">The name of the collection to animate</param>
        public void Animate(String aName)
        {

            // Set the animation set
            iCurrentAnimationSet = aName;

            // First Frame Grab
            fGetFirstFrame();

        }

        /// <summary>
        /// Sets the CurrentFrame object to the next frame in the sequence
        /// </summary>
        private void fGetNextFrame()
        {

            // Get the collection
            List<FrameCollection.Frame> oFrames = Frames[iCurrentAnimationSet].ToList();

            // Find the location of the current frame
            Int32 nCurrentIndex = oFrames.IndexOf(CurrentFrame);

            // Do some sanity checking
            if (nCurrentIndex == -1) throw new ArgumentException("CurrentFrame is invalid!");
            if (nCurrentIndex == oFrames.Count() - 1) nCurrentIndex = 0; else nCurrentIndex++;

            // Now set the current frame to the next frame
            CurrentFrame = oFrames[nCurrentIndex];

        }

        /// <summary>
        /// Sets the CurrentFrame to the first frame of the animation set.
        /// </summary>
        private void fGetFirstFrame()
        {

            // Get the collection
            CurrentFrame = Frames[iCurrentAnimationSet].First();

        }

    }

    /// <summary>
    /// A KVP collection object to describe a current set of animations frames for
    /// a given sprite animation.
    /// 
    /// The key is the name of the collection of frames to play
    /// The value object is a collection of frames that make up the given animation
    /// </summary>
    public sealed class FrameCollection : Dictionary<String, IEnumerable<FrameCollection.Frame>>
    {

        /// <summary>
        /// The frame class is a simple object that describes an
        /// individual frame in a collection of frames for a 2 dimensional animation.
        /// </summary>
        public sealed class Frame
        {

            /// <summary>
            /// The name of this frame.
            /// </summary>
            /// <remarks>This is quite useful for debugging</remarks>
            public String Name { get; set; }

            /// <summary>
            /// The physical frame dimensions.
            /// </summary>
            public Rectangle Location { get; set; }

            /// <summary>
            /// The number of frames to wait before advancing to the next frame in the sequence
            /// </summary>
            public Int32 Frames { get; set; }

        }

    }

}
